package com.binance.zkconfig.processor;

import com.binance.zkconfig.app.annotation.ZkValue;
import com.binance.zkconfig.app.model.DynamicStringDescProperty;
import com.netflix.config.DynamicBooleanProperty;
import com.netflix.config.DynamicDoubleProperty;
import com.netflix.config.DynamicFloatProperty;
import com.netflix.config.DynamicIntProperty;
import com.netflix.config.DynamicLongProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.config.DynamicStringMapProperty;
import com.netflix.config.DynamicStringProperty;
import com.netflix.config.DynamicStringSetProperty;
import com.netflix.config.Property;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created by Fei.Huang on 2018/8/28.
 */
public class ZkValueAnnotationProcessor implements BeanPostProcessor, PriorityOrdered {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<? extends Object> clazz = bean.getClass();
        List<Field> fields = new ArrayList<>();
        collectAllFields(fields, clazz);
        processFields(bean, fields);
        return bean;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    private void collectAllFields(List<Field> fields, Class<?> type) {
        fields.addAll(Arrays.asList(type.getDeclaredFields()));
        if (type.getSuperclass() != null) {
            collectAllFields(fields, type.getSuperclass());
        }
    }

    private void processFields(Object bean, List<Field> declaredFields) {
        for (Field field : declaredFields) {
            ZkValue annotation = AnnotationUtils.getAnnotation(field, ZkValue.class);
            if (annotation == null) {
                continue;
            }
            Class<?> fieldType = field.getType();
            String annotationValue = annotation.value();
            if (StringUtils.isEmpty(annotationValue) || !annotationValue.startsWith("${")
                    || !annotationValue.endsWith("}")) {
                throw new IllegalArgumentException(
                        String.format("Invalid @ZkValue(${key:defaultValue}) for field %s", field));
            }
            String[] keyValue = annotationValue.substring(2, annotationValue.length() - 1).split(":");
            String defaultValue = keyValue[0];
            String value = keyValue.length >= 2 ? keyValue[1] : null;
            String extValue = keyValue.length >= 3 ? keyValue[2] : null;
            Property<?> property = createProperty(field, fieldType, defaultValue, value, extValue);
            ReflectionUtils.makeAccessible(field);
            ReflectionUtils.setField(field, bean, property);
        }
    }

    /**
     * 支持类型： String, Boolean, Integer, Long, Double, Float, StringSet, StringMap
     *
     * @param field
     * @param fieldType
     * @param key
     * @param defaultValue
     * @param extValue
     * @return
     */
    private Property<?> createProperty(Field field, Class<?> fieldType, String key, String defaultValue, String extValue) {
        Property<?> property;

        if (DynamicStringDescProperty.class.isAssignableFrom(fieldType)) {
            property = new DynamicStringDescProperty(key, defaultValue != null ? defaultValue : null);
        } else if (DynamicStringProperty.class.isAssignableFrom(fieldType)) {
            property = DynamicPropertyFactory.getInstance().getStringProperty(key, defaultValue != null ? defaultValue : null);
        } else if (DynamicBooleanProperty.class.isAssignableFrom(fieldType)) {
            assertVNotNull(defaultValue, DynamicBooleanProperty.class.getSimpleName());
            property = DynamicPropertyFactory.getInstance().getBooleanProperty(key, Boolean.valueOf(defaultValue));
        } else if (DynamicIntProperty.class.isAssignableFrom(fieldType)) {
            assertVNotNull(defaultValue, DynamicIntProperty.class.getSimpleName());
            property = DynamicPropertyFactory.getInstance().getIntProperty(key, Integer.parseInt(defaultValue));
        } else if (DynamicLongProperty.class.isAssignableFrom(fieldType)) {
            assertVNotNull(defaultValue, DynamicLongProperty.class.getSimpleName());
            property = DynamicPropertyFactory.getInstance().getLongProperty(key, Long.parseLong(defaultValue));
        } else if (DynamicDoubleProperty.class.isAssignableFrom(fieldType)) {
            assertVNotNull(defaultValue, DynamicDoubleProperty.class.getSimpleName());
            property = DynamicPropertyFactory.getInstance().getDoubleProperty(key, Double.valueOf(defaultValue));
        } else if (DynamicFloatProperty.class.isAssignableFrom(fieldType)) {
            assertVNotNull(defaultValue, DynamicFloatProperty.class.getSimpleName());
            property = DynamicPropertyFactory.getInstance().getFloatProperty(key, Float.valueOf(defaultValue));
        } else if (DynamicStringSetProperty.class.isAssignableFrom(fieldType)) {
            assertExtIsNotVNotNull(defaultValue, extValue, DynamicStringSetProperty.class.getSimpleName(),
                    "listDelimiterRegex");
            property = extValue != null ? new DynamicStringSetProperty(key, defaultValue, extValue)
                    : new DynamicStringSetProperty(key, defaultValue);
        } else if (DynamicStringMapProperty.class.isAssignableFrom(fieldType)) {
            assertExtIsNotVNotNull(defaultValue, extValue, DynamicStringMapProperty.class.getSimpleName(),
                    "mapEntryDelimiterRegex");
            property = extValue != null ? new DynamicStringMapProperty(key, defaultValue, extValue)
                    : new DynamicStringMapProperty(key, defaultValue);
        } else {
            throw new IllegalArgumentException(String
                    .format("ZkConfig invalid type for field %s, it should be Archaius DynamicXXXProperty", field));
        }

        return property;
    }

    private void assertVNotNull(String value, String propertyName) {
        Assert.notNull(value, String.format("ZkConfig default value for %s cannot be null", propertyName));
    }

    private void assertExtIsNotVNotNull(String value, String ext, String propertyName, String extension) {
        Assert.isTrue(null == ext || null != value, String.format(
                "ZkConfig default value for %s cannot be null if %s is also specified", propertyName, extension));
    }
}
